package psn.kiko.service.business.system;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;
import psn.kiko.config.properties.FileUploadProperties;
import psn.kiko.constant.MessageConstant;
import psn.kiko.excepion.FileOperationException;
import psn.kiko.mapper.business.system.FileMapper;
import psn.kiko.pojo.PageResult;
import psn.kiko.pojo.dto.FileDTO;
import psn.kiko.pojo.entity.system.FileEntity;
import psn.kiko.pojo.vo.FileVO;
import psn.kiko.pojo.vo.ServerPhysicFileVO;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class FileService{
	@Autowired
	FileMapper fileMapper;
	@Autowired
	FileUploadProperties fileUploadProperties;
	@Autowired
	ServerProperties serverProperties;
	
	/**
	 * 通过文件minme类型前缀分页查询文件信息
	 *
	 * @param mineTypePrefix 如audio,video,image等
	 */
	public PageResult<Object> pagingFilesByMinePrefix(String mineTypePrefix,int pageNUm,int pageSize){
		FileEntity fileEntity = FileEntity.builder().
				mimePrefix(mineTypePrefix).
				build();
		Page<FileEntity> page = PageHelper.startPage(pageNUm,pageSize);
		List<FileVO> fileVOS = fileMapper.selectList(fileEntity);
		
		PageResult<Object> pageResult = PageResult.builder()
				.data(fileVOS)
				.total(((int) page.getTotal()))
				.build();
		return pageResult;
	}
	
	/**
	 * 删除所有已上传文件
	 */
	public ArrayList<File> deleteAll(){
		try{
			String uploadRootPath = null;
			uploadRootPath = getUploadRootPath();
			File file = new File(uploadRootPath);
			//收集被删除的文件名
			ArrayList<File> list = new ArrayList<>();
			deleteDirectoryRecusively(file,list);
			return list;
		}catch(FileNotFoundException e){
			e.printStackTrace();
			throw new FileOperationException(MessageConstant.FILE_DELETION_FAIL);
		}
	}
	
	/**
	 * 递归删除
	 */
	private void deleteDirectoryRecusively(File file,ArrayList<File> list){
		if(!file.exists()){
			throw new FileOperationException(MessageConstant.FILE_NOT_EXIST);
		}
		if(file.isDirectory()){
			File[] children = file.listFiles();
			for(File child: children){
				deleteDirectoryRecusively(child,list);
			}
			file.delete();
		}else{
			list.add(file);
			file.delete();
		}
	}
	
	/**
	 * 批量存储文件到磁盘，并批量保存文件信息至数据库
	 */
	public List<String> batchAddFile(MultipartFile[] files){
		ArrayList<String> uploadedFiles = new ArrayList<>();
		ArrayList<FileEntity> fileEntities = new ArrayList<>();
		try{
			String rootPath = getUploadRootPath();
			for(MultipartFile file: files){
				LocalDateTime now = LocalDateTime.now();
				String dateTimePrefix = now.format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmssSSS"));
				//收集文件信息
				//文件mime类型前缀
				String mimePrefix = file.getContentType().split("/")[0];
				//文件尺寸（bytes）
				long size = file.getSize();
				//原名带扩展名
				String originalFileName = file.getOriginalFilename();
				//扩展名
				String extend = originalFileName.substring(originalFileName.lastIndexOf('.') + 1);
				//存储名
				String storeFileName = dateTimePrefix + file.getOriginalFilename();
				File f = new File(rootPath,storeFileName);
				//存储绝对路径
				String storeAbsolutePath = f.getAbsolutePath();
				//访问url
				String visitUrl = "http://localhost:" + serverProperties.getPort() + "/" + fileUploadProperties.getPath() +
						"/" + f.getName();
				
				if(!f.getParentFile().exists()){
					if(!f.getParentFile().mkdirs()){
						return null;
					}
				}
				// 保存文件到磁盘
				file.transferTo(f);
				
				FileEntity fileEntity = FileEntity.builder().
						size(size).
						mimePrefix(mimePrefix).
						originalFileName(originalFileName).
						extend(extend).
						storeFileName(storeFileName).
						storeAbsolutePath(storeAbsolutePath).
						visitUrl(visitUrl).
						build();
				
				uploadedFiles.add(visitUrl);
				fileEntities.add(fileEntity);
			}
		}catch(IOException e){
			e.printStackTrace();
			throw new FileOperationException(MessageConstant.FILE_UPLOAD_FAIL);
		}
		//保存文件信息到数据库
		fileMapper.insertList(fileEntities);
		return uploadedFiles;
	}
	
	/**
	 * 根据文件绝对路径下载文件
	 */
	public void download(
			@ApiParam(allowMultiple = true, value = "文件名") String absolutePath,
			HttpServletResponse response) throws FileNotFoundException{
		File file = new File(absolutePath);
		// 若文件不存在
		if(file == null||!file.exists()){
			throw new FileOperationException(MessageConstant.FILE_NOT_EXIST);
		}
		
		try(
				OutputStream outputStream = response.getOutputStream();
				FileInputStream fileInputStream = new FileInputStream(file)
		){
			//设置响应信息
			response.setContentType("application/octect-stream");
			//设置文件下载名称
			response.setHeader("Content-Disposition",
					"attachment;filename=\"" + URLEncoder.encode(file.getName(),"utf-8") + "\"");
			byte[] buf = new byte[1024*1024];
			int len;
			
			//读取文件，并写到客户端
			while((len = fileInputStream.read(buf)) > 0){
				outputStream.write(buf,0,len);
			}
			outputStream.flush();
		}catch(IOException e){
			e.printStackTrace();
			throw new FileOperationException(MessageConstant.FILE_DOWNLOAD_FAIL);
		}
	}
	
	/**
	 * 获取文件上传的根路径,如F:/Idea_WorkSpace/springboot-mysql-auth/target/classes/uploadfiles
	 */
	private String getUploadRootPath() throws FileNotFoundException{
		return getSystemClasspath() + File.separator + fileUploadProperties.getPath() + File.separator;
	}
	
	/**
	 * 获取classpath对应的目录
	 */
	private String getSystemClasspath() throws FileNotFoundException{
		String classPath = ResourceUtils.getURL("classpath:").getPath();
		return new File(classPath).getAbsolutePath();
	}
	
	/**
	 * 删除物理文件、及其数据库信息
	 */
	@Transactional
	public Integer deleteByDtos(List<FileDTO> files){
		List<Long> ids = files.stream().
				map(FileDTO::getId).
				collect(Collectors.toList());
		List<String> filePaths = files.stream()
				.map(FileDTO::getStoreAbsolutePath)
				.collect(Collectors.toList());
		filePaths.forEach(absolutePath->{
			File file = new File(absolutePath);
			if(file.exists()){
				file.delete();
			}
		});
		return fileMapper.deleteListByIds(ids);
	}
	
	
	/**
	 * 获取指定目录的子文件信息,访问范围至多到classpath对应的目录，即classes
	 */
	public List<ServerPhysicFileVO> childrenOfDirLTClasspath(String dirAbsolutePath) throws FileNotFoundException{
		// 能够访问的最顶层目录
		String rootFilePath = getSystemClasspath();
		return listChildrenOfDirectoryByAbsolutePath(dirAbsolutePath,rootFilePath);
	}
	
	/**
	 * 获取指定目录的子文件信息，访问范围至多到classpath下的controller对应的目录，即classes/psn/kiko/controller
	 */
	public List<ServerPhysicFileVO> childreOfDirLTController(String dirAbsolutePath) throws FileNotFoundException{
		// 能够访问的最顶层目录
		String rootPath = getSystemClasspath() + "\\psn\\kiko\\controller";
		return listChildrenOfDirectoryByAbsolutePath(dirAbsolutePath,rootPath);
	}
	
	/**
	 * 列出指定目录的直接子文件集合
	 *
	 * @param dirAbsolutePath     指定的目录（绝对路径）
	 * @param rootDirAbsolutePath 限定访问的最大范围（绝对路径），若访问的目录超出此范围，则将其重置为该目录
	 */
	private List<ServerPhysicFileVO> listChildrenOfDirectoryByAbsolutePath(
			String dirAbsolutePath,
			String rootDirAbsolutePath
	) throws FileNotFoundException{
		// 当前目录
		File curDir = new File(dirAbsolutePath);
		
		// 如果当前文件不存在或者不是目录
		if(curDir == null||!curDir.exists()||!curDir.isDirectory()){
			return null;
		}
		
		File rootDir = new File(rootDirAbsolutePath);
		// 若可访问的根目录不存在
		if(rootDir == null||!rootDir.exists()||!rootDir.isDirectory()){
			return null;
		}
		
		String rootAbsolutePath = rootDir.getAbsolutePath();
		// 若想访问的目录已经超出指定的访问范围，则将其重置为最大访问范围
		if(dirAbsolutePath.compareTo(rootAbsolutePath) < 0){
			curDir = new File(rootDirAbsolutePath);
		}
		
		// 当前目录子文件集合
		File[] files = curDir.listFiles();
		
		// 子文件信息集合
		List<ServerPhysicFileVO> fileVOs = Arrays.stream(files).map(f->{
			// 获取每个子文件的所需信息
			ServerPhysicFileVO fileVO = ServerPhysicFileVO.builder()
					.directoryOrNot(f.isDirectory())
					.fullName(f.getName())
					.absolutePath(f.getAbsolutePath())
					.build();
			return fileVO;
			
		}).collect(Collectors.toList());
		
		return fileVOs;
	}
	
	/**
	 * 返回指定文件所在的文件夹的父文件夹下的文件信息集合
	 *
	 * @param specificFile 指定文件
	 */
	public List<ServerPhysicFileVO> returnParentDirOfCurDirLTClasspath(String specificFile) throws FileNotFoundException{
		List<ServerPhysicFileVO> fileVOS = null;
		File file = new File(specificFile);
		// 获取当前文件所在文件夹的父文件夹下的子文件集合
		if(file != null&&file.exists()){
			File parentFile = file.getParentFile();
			if(parentFile != null&&parentFile.exists()){
				File grandFile = parentFile.getParentFile();
				if(grandFile != null&&grandFile.exists()){
					fileVOS = childrenOfDirLTClasspath(grandFile.getAbsolutePath());
				}
			}
		}
		return fileVOS;
	}
	
	/**
	 * 返回指定文件所在的文件夹的父文件夹下的文件信息集合
	 *
	 * @param specificFile 指定文件
	 */
	public List<ServerPhysicFileVO> returnParentDirOfCurDirLTController(String specificFile) throws FileNotFoundException{
		List<ServerPhysicFileVO> fileVOS = null;
		File file = new File(specificFile);
		// 获取当前文件所在文件夹的父文件夹下的子文件集合
		if(file != null&&file.exists()){
			File parentFile = file.getParentFile();
			if(parentFile != null&&parentFile.exists()){
				File grandFile = parentFile.getParentFile();
				if(grandFile != null&&grandFile.exists()){
					fileVOS = childrenOfDirLTClasspath(grandFile.getAbsolutePath());
				}
			}
		}
		return fileVOS;
	}
	
	public List<FileDTO> getFilesBySrc(List<String> unUsedImageSrcList){
		return fileMapper.selectListBySrc(unUsedImageSrcList);
	}
	
	public List<FileDTO> getFileByIds(ArrayList<Long> fileIds){
		return fileMapper.selectListById(fileIds);
	}
}
